package com.cirnoworks.utils.text;

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.concurrent.Callable;

public class MeasureCache<V> {
	public static int hash(TextFragment tf, int begin, int end) {
		int count = end - begin;
		if (count == 0) {
			return 0;
		}
		int fid = tf.getFontId();
		int size = tf.getSize();
		int misc = tf.getMisc();
		int hash = fid << 16 + size + misc;
		for (int i = begin; i < count + begin; i++) {
			hash = tf.charAt(i) + ((hash << 5) - hash);
		}
		return hash & 0x7fffffff;
	}

	private static class Entry<V> {
		private char[] content;
		private int fid;
		private int size;
		private int misc;
		private V value;
		private int hash;

		public boolean compareKey(TextFragment tf, int begin, int end) {
			int len = content.length;
			if (end - begin != len) {
				return false;
			}
			if (fid != tf.getFontId()) {
				return false;
			}
			if (size != tf.getSize()) {
				return false;
			}
			if (misc != tf.getMisc()) {
				return false;
			}
			char[] k = content;
			for (int i = 0; i < len; i++) {
				if (k[i] != tf.charAt(begin + i)) {
					return false;
				}
			}
			return true;
		}

		public V getValue() {
			return value;
		}

		public int getHash() {
			return hash;
		}

		public void setup(TextFragment tf, int begin, int end, V value) {
			content = new char[end - begin];
			for (int i = 0, max = end - begin; i < max; i++) {
				content[i] = tf.charAt(begin + i);
			}
			fid = tf.getFontId();
			size = tf.getSize();
			misc = tf.getMisc();
			hash = hash(tf, begin, end);
			this.value = value;
		}
	}

	public ArrayList<Entry<V>>[] content;
	public ArrayDeque<Entry<V>> queue;
	public int cap;

	public MeasureCache(int cap) {
		content = new ArrayList[(int) (cap * 1.4f)];
		this.cap = cap;
		queue = new ArrayDeque<MeasureCache.Entry<V>>(cap);
	}

	private Entry<V> find(ArrayList<Entry<V>> cntt, TextFragment tf, int begin,
			int end) {
		for (int i = 0, max = cntt.size(); i < max; i++) {
			Entry<V> entry = cntt.get(i);
			if (entry.compareKey(tf, begin, end)) {
				return entry;
			}
		}
		return null;
	}

	public V putOnNotExist(TextFragment tf, int begin, int end,
			Callable<V> valueGenerater) {
		int pos = hash(tf, begin, end) % cap;
		ArrayList<Entry<V>> cntt = content[pos];
		Entry<V> entry = null;
		if (cntt != null) {
			entry = find(cntt, tf, begin, end);
			if (entry != null) {
				return entry.getValue();
			}
		} else {
			cntt = new ArrayList<Entry<V>>(4);
			content[pos] = cntt;
		}
		try {
			V value = valueGenerater.call();
			if (queue.size() >= cap) {
				entry = queue.pollFirst();
				ArrayList<Entry<V>> tmpCntt = content[entry.getHash() % cap];
				tmpCntt.remove(entry);
			} else {
				entry = new Entry<V>();
			}
			entry.setup(tf, begin, end, value);
			cntt.add(entry);
			queue.addLast(entry);
			return value;
		} catch (Exception e) {
			if (e instanceof RuntimeException) {
				throw (RuntimeException) e;
			} else {
				throw new RuntimeException(e);
			}
		}
	}
}